using System;
using System.Reflection;
using System.Web;
using System.Web.UI;
using Sedna.Core.Common.Objects.Navigation;
using Sedna.Core.Controller;
using Sedna.Core.Controller.Configurations;

namespace Sedna.Core.DataBinding
{

	public class DataBinder: IDataBinder
	{
		private Control control;
		private IConfiguration configuration;
		private IExecutionContext executionContext;
		private Type configType;
		private Type controlType;

		public DataBinder(Control control, IConfiguration configuration, IExecutionContext executionContext)
		{
			this.control=control;
			this.controlType=control.GetType();
			this.configuration=configuration;
			this.configType=configuration.GetType();
			this.executionContext=executionContext;
		}

	
		#region Convenience methods
		
		public bool Bind(string property)
		{
			return Bind(property, GetTranslatorFromConfig(property));
		}

		public bool Unbind(string property)
		{
			return Unbind(property, configuration.AllowFirstUnbind(property));
		}

        public bool Unbind(string property, bool AllowFirstUnbind)
        {
            return Unbind(property, GetParserFromConfig(property), AllowFirstUnbind);
        }

		public bool Bind(object root, string property)
		{
            return Bind(root, property, GetTranslatorFromConfig(property));
		}

		public bool Bind(string property, IDataTranslator translator)
		{
			return Bind(null, property, (translator != null) ? translator : GetTranslatorFromConfig(property));
		}

		public bool Unbind(object root, string property)
		{
            return Unbind(root, property, configuration.AllowFirstUnbind(property));
		}

        public bool Unbind(object root, string property, bool AllowFirstUnbind)
        {
            return Unbind(root, property, GetParserFromConfig(property), AllowFirstUnbind);
        }


		public bool Unbind(string property, IDataTranslator parser)
		{
            return Unbind(property, parser, configuration.AllowFirstUnbind(property));
		}

        public bool Unbind(string property, IDataTranslator parser, bool AllowFirstUnbind)
        {
            return Unbind(null, property, (parser != null) ? parser : GetParserFromConfig(property), AllowFirstUnbind);
        }

		public bool SetPropertyValue(string property, object value)
		{
			return SetPropertyValue(null, property, value);
		}

		public bool GetPropertyValue(string property, out object value)
		{
			return GetPropertyValue(null, property, out value);
		}

		public void Bind(object root, string[] properties)
		{
			if (properties==null||properties.Length==0)
			{
				return;
			}
			foreach (string property in properties)
			{
				Bind(root, property, GetTranslatorFromConfig(property));
			}
		}

		public void Unbind(object root, string[] properties)
		{
            if (properties == null || properties.Length == 0)
            {
                return;
            }
            foreach (string property in properties)
            {
                Unbind(root, property, GetParserFromConfig(property), configuration.AllowFirstUnbind(property));
            }
		}

        public void Unbind(object root, string[] properties, bool AllowFirstUnbind)
        {
            if (properties == null || properties.Length == 0)
            {
                return;
            }
            foreach (string property in properties)
            {
                Unbind(root, property, GetParserFromConfig(property), AllowFirstUnbind);
            }
        }

	    public void Unbind(string[] properties, IDataTranslator parser)
	    {
            if (properties == null || properties.Length == 0)
            {
                return;
            }
            foreach (string property in properties)
            {
                Unbind(null, property, (parser != null) ? parser : GetParserFromConfig(property), configuration.AllowFirstUnbind(property));
            }
	    }

	    public void Unbind(string[] properties, IDataTranslator parser, bool AllowFirstUnbind)
	    {
            if (properties == null || properties.Length == 0)
            {
                return;
            }
            foreach (string property in properties)
            {
                Unbind(null, property, (parser != null) ? parser : GetParserFromConfig(property), AllowFirstUnbind);
            }
	    }

	    public void Bind(string[] properties)
		{
			Bind(null, properties);
		}

	    public void Bind(string[] properties, IDataTranslator translator)
	    {
            if (properties == null || properties.Length == 0)
            {
                return;
            }
	        
            foreach (string property in properties)
            {
                Bind(null, property, (translator != null) ? translator : GetTranslatorFromConfig(property));
            }
	    }

	    public void Unbind(string[] properties)
		{
            Unbind(null, properties);
		}

        public void Unbind(string[] properties, bool AllowFirstUnbind)
        {
            Unbind(null, properties, AllowFirstUnbind);
        }

        public bool Unbind(object root, string property, IDataTranslator parser)
        {
            return Unbind(root, property, parser, configuration.AllowFirstUnbind(property));
        }
	    
        #endregion

		public bool SetPropertyValue(object root, string property, object value)
		{
            executionContext.CurrentScopePrefix = control.ClientID;
			string unbindPath=GetUnbindPath(property);
			if (unbindPath==null)
			{
				return false; //no path specified
			}
			ScopePathParser.Instance.Parse(root, unbindPath).Value=value;
			return true;
		}

		public bool GetPropertyValue(object root, string property, out object value)
		{
            executionContext.CurrentScopePrefix = control.ClientID;
            value = null;
			string bindPath=GetBindPath(property);
			if (bindPath==null)
			{
				return false; //no path specified
			}
			
		    value=ScopePathParser.Instance.Parse(root, bindPath).Value;
		    if (value is TranslatorValueWrapper)
		    {
                value = ((TranslatorValueWrapper) value).GetValue(typeof (object));
		    }
			return true;
		}

		public bool Bind(object root, string property, IDataTranslator translator)
		{
            executionContext.CurrentScopePrefix = control.ClientID;
            
		    string bindPath = GetBindPath(property);

            if (String.IsNullOrEmpty(bindPath))
			{
				return false; //no path specified
			}
			
		    object propertyValue = ScopePathParser.Instance.Parse(root, bindPath).Value;
			
		    if (propertyValue is TranslatorValueWrapper)
			{
				propertyValue = ((TranslatorValueWrapper) propertyValue).GetValue(typeof(object));
			}
		    
		    propertyValue = translator != null ? translator.Translate(control, propertyValue) : propertyValue;
		    
			SetControlPropertyValue(property, propertyValue);
			
		    return true;
		}

        public bool Unbind(object root, string property, IDataTranslator translator, bool AllowFirstUnbind)
        {
            executionContext.CurrentScopePrefix = control.ClientID;
            
            if (!BasePage.Current.IsPostBack && !AllowFirstUnbind)
            {
                return false;
            }
            
            string unbindPath = GetUnbindPath(property);
            
            if (unbindPath == null)
            {
                return false; //no path specified
            }
            
            object propertyValue = GetControlPropertyValue(property);
            
            IScopePath scopePath = ScopePathParser.Instance.Parse(root, unbindPath);
            
            //scopePath.Value=translator!=null?translator.Translate(control, propertyValue):propertyValue;
            
            scopePath.Value = translator != null ? new TranslatorValueWrapper(translator, propertyValue, control) : propertyValue;
            
            return true;
        }

		#region Helper methods
	    
	    private IDataTranslator GetTranslatorFromConfig(string property)
	    {
            return (configuration == null) ? null : configuration.GetDataTranslator(property);
	    }

        private IDataTranslator GetParserFromConfig(string property)
        {
            return (configuration == null) ? null : configuration.GetDataParser(property);
        }
	    
		private void SetControlPropertyValue(string property, object value)
		{
            PropertyInfo propertyInfo = controlType.GetProperty(property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			if (propertyInfo==null)
			{
				//TODO: log exception
				throw new NotSupportedException(String.Format("Class {0} has no property named \"{1}\".", controlType, property));
			}
			propertyInfo.SetValue(control, value, null);
		}

		private object GetControlPropertyValue(string property)
		{
			PropertyInfo propertyInfo=controlType.GetProperty(property, BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance);
			if (propertyInfo==null)
			{
				//TODO: log exception
				throw new NotSupportedException(String.Format("Class {0} has no property named \"{1}\".", controlType, property));
			}
			return propertyInfo.GetValue(control, null);
		}

		private string GetConfigPropertyValue(string property)
		{
			PropertyInfo propertyInfo=configType.GetProperty(property, typeof(string));
			if (propertyInfo==null)
			{
				//TODO: log exception
				//return null;	
				throw new NotSupportedException(String.Format("Configuration class {0} has no property \"{1}\".", configType, property));
			}
			return (string)propertyInfo.GetValue(configuration, null);
		}

		private string GetUnbindPath(string property)
		{
			try
			{
				return GetConfigPropertyValue(String.Format("{0}UnbindPath", property));	
			}
			catch (Exception e)
			{
				throw new NotSupportedException(String.Format("Unbinding for property \"{0}\" is not supported", property), e);
			}
		}

		private string GetBindPath(string property)
		{
			try
			{
				return GetConfigPropertyValue(String.Format("{0}BindPath", property));
			}
			catch (Exception e)
			{
				throw new NotSupportedException(String.Format("Binding for property \"{0}\" is not supported", property), e);
			}
		}
	    
		#endregion

		public class TranslatorValueWrapper : IValueWrapper
		{
			IDataTranslator translator;
			object value;
			Control control;

			public TranslatorValueWrapper(IDataTranslator translator, object value, Control control)
			{
				this.translator = translator;
				this.value = value;
				this.control = control;
			}

			public object GetValue(Type targetType)
			{
				return translator.Translate(control, value, targetType);
			}
		}
	}
}
